from collections import OrderedDict
import click
from backtrader.feeds.binancedata import BinanceFinanceCSVData
from backtrader.sizers import AllInSizer
import backtrader as bt
from backtrader.utils import logger, datehelper
from datetime import datetime, timedelta
import json
from backtrader.utils.cmd import cli
from backtrader.plot.show import ShowPlot
import os


class BtRun:
    def __init__(self):
        self.logger = logger.getLogger(__name__)

    def run(self, **kwargs):
        for k, v in kwargs.items():
            self.logger.info(f"{k}={v}")

        if "runonce" in kwargs:
            runonce = kwargs["runonce"]
        else:
            runonce = True
        # Create a cerebro
        cerebro = bt.Cerebro(optdatas=False, optreturn=False, runonce=runonce)

        cerebro.broker.set_cash(kwargs["init_cash"])
        cerebro.broker.set_shortcash(True)
        if "slippage" in kwargs and kwargs["slippage"]:
            if kwargs["slippage"]["type"] == "fixed":
                cerebro.broker.set_slippage_fixed(kwargs["slippage"]["value"], slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
            if kwargs["slippage"]["type"] == "perc":
                cerebro.broker.set_slippage_perc(kwargs["slippage"]["value"], slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
        # cerebro.broker.set_slippage_perc(perc=0.001)
        cerebro.broker.setcommission(commission=kwargs["comm"])
        cerebro.addsizer(AllInSizer, percents=kwargs["size"])

        # Get the dates from the args
        fromdate = datetime.strptime(kwargs["start_date"], '%Y-%m-%d')
        todate = datetime.strptime(kwargs["ended_date"], '%Y-%m-%d')

        tf = kwargs["tf"]
        timeframe, compression, total_minutes = bt.utils.datehelper.parse_timeframe(tf)

        ws = kwargs["workspace"] if "workspace" in kwargs else "/Users/wudi/Workspace/fund/workspace"
        symbols = kwargs["symbol"]
        for symbol in symbols:
            _symbol_name = symbol.replace("/", "_")
            data = BinanceFinanceCSVData(
                dataname=f"{ws}/data/binance/market/spot/{tf}/{_symbol_name}.csv",
                fromdate=fromdate,
                todate=todate,
                timeframe=timeframe,
                compression=compression
            )
            cerebro.adddata(data)

            resample = kwargs["resample"]
            if resample:
                _timeframe, _compression, _total_minutes = bt.utils.datehelper.parse_timeframe(resample)
                cerebro.resampledata(
                    data,
                    name=f"{_symbol_name}_{resample}",
                    boundoff=_total_minutes,
                    bar2edge=False,
                    adjbartime=True,
                    rightedge=False,
                    timeframe=_timeframe,
                    compression=_compression, )

        symbol_name = symbols[0].replace("/", "_")

        # cerebro.resampledata(data, "1h", timeframe=bt.TimeFrame.Minutes,  compression=60)
        # cerebro.adddata(data)  # Add the data to cerebro
        # Add the strategy
        strat = kwargs["strat"]
        if not strat:
            self.logger.exception("parameter [strat] must be not none.")
            return
        strat = __import__(strat)
        strat_names = [e for e in strat.__dict__.keys() if e.endswith("Strategy")]
        if not strat_names:
            raise Exception(f"策略名称必须以Strategy结尾.")
        strat_clz = strat_names[0]

        opt = kwargs["opt"]
        filter_exp = kwargs["filter_exp"]
        params = kwargs["params"]

        comm = int(kwargs['comm'] * 10000)
        # 补充标识
        cash = int(kwargs['init_cash'])
        start = kwargs["start_date"].replace('-', '')
        end = kwargs["ended_date"].replace('-', '')

        params["size"] = kwargs["size"]
        params["comm"] = comm
        params["init_cash"] = cash
        params["start_date"] = int(start)
        params["ended_date"] = int(end)
        params["timeframe"] = tf

        clz = strat.__dict__[strat_clz]
        if opt:
            cerebro.optstrategy(
                clz,
                filter_exp=filter_exp,
                **params
            )
        else:
            cerebro.addstrategy(clz, **params)

        out_style = f"{clz.alias[0]}/{symbol_name}/"
        cerebro.addanalyzer(bt.analyzers.SimpleReport, csv=True, out=out_style)
        cerebro.addanalyzer(bt.analyzers.Trades, csv=True, out=out_style)
        cerebro.addanalyzer(bt.analyzers.Orders, csv=True, out=out_style)
        cerebro.addanalyzer(bt.analyzers.NetAssetValue, csv=True, out=out_style)
        cerebro.addanalyzer(bt.analyzers.Holdings, csv=True, out=out_style)
        cerebro.addanalyzer(bt.analyzers.TimeDrawDownHistory, csv=True, out=out_style)
        # cerebro.addwriter(bt.WriterFile, csv=True, out="test.csv", rounding=4)
        cerebro.run()  # And run it
        if kwargs["plot"]:
            cerebro.plot()

        if "benchmark" in kwargs and kwargs["benchmark"]:
            # save plot
            fdir = "."
            strats = [clz.alias[0]]

            for strategy in cerebro.runstrats:
                params = bt.Analyzer.get_params1(strat=strategy[0])
                name = "_".join(map(lambda x: str(x), params.values()))
                try:
                    ShowPlot(fdir, symbol_name, kwargs["benchmark"], strats, show_fig=False).show(name=name, figsize=(30, 15))
                except Exception as e:
                    self.logger.error(e)
                    self.logger.warning("Plot benchmark error.")

@cli.command()
@click.option("--workspace", default="/Users/wudi/Workspace/fund/workspace")
@click.option("--symbol", default="ETH/USDT")
@click.option('--start', default="2020-08-17")
@click.option('--end', default="2021-08-19")
@click.option('--tf', default="15m")
@click.option('--strat', default=None)
@click.option('--params', default="{}")
@click.option('--filter_exp', default=None)
@click.option('--resample', default=None)
@click.option('--cash', default=10000)
@click.option('--comm', default=0.0003)
@click.option('--size', default=90)
@click.option('--opt', default=False)
@click.option('--plot', default=False)
def run(**kwargs):
    kwargs["params"] = json.loads(kwargs["params"].replace("'", "\""))
    BtRun().run(**kwargs)

@click.group()
def btrun():
    pass

btrun.add_command(run)

if __name__ == "__main__":
    btrun()

